feat(daemon): restore worktree isolation on session load/resume - #7262
Conversation
Add support for creating sessions in isolated git worktrees from the Web Shell, enabling multiple tasks to run in parallel within the same workspace without polluting the main working directory. Daemon: - POST /session accepts optional worktree param, creates worktree via GitWorktreeService, relocates session via changeSessionCwd - Worktree metadata persisted in SessionEntry, BridgeSessionSummary, and sidecar file (<sessionId>.worktree.json) for daemon restart recovery - GET /workspaces/:workspace/git supports ?cwd= for worktree-scoped git status queries (path.resolve + containment check) SDK: - CreateSessionRequest/DaemonSession/DaemonSessionSummary gain worktree field; DaemonSessionClient exposes worktree getter - WorkspaceDaemonClient.workspaceGit() accepts optional cwd param Web Shell: - Workspace branch pill dropdown offers 'New Worktree Task' (git repos only) with purple GitForkIcon and description - Git chip turns purple with GitForkIcon for worktree sessions - Session list shows inline ⑂ badge for worktree sessions - Empty-state welcome badge explains worktree isolation - Git status queries target worktree path, not workspace root - session_cwd_changed event filtered from chat transcript Design doc: docs/design/2026-07-19-webshell-worktree-sessions.md
After daemon restart, loading a worktree session now: 1. Reads the worktree sidecar file (<sessionId>.worktree.json) 2. Calls changeSessionCwd to relocate the session into the worktree 3. Populates the bridge entry via setSessionWorktree so GET /session/:id/status returns worktree metadata 4. Includes worktree info in the load/resume response If the worktree directory was removed, the session continues in the main workspace without isolation (graceful degradation). Bridge: adds setSessionWorktree(sessionId, worktree) to AcpSessionBridge interface and implementation.
Re-verification ✅ — the PR 7221 restart-persistence gap is fixedThis is the follow-up to the finding I reported on #7221 (worktree session disappeared from the session list after a daemon restart). I rebuilt on top of this branch ( What the fix does (and why it resolves the finding)
Re-verification results (real
|
| Check | PR 7221 | PR 7262 |
|---|---|---|
Worktree session listed after daemon restart (with worktree metadata) |
❌ dropped (total=0) |
✅ total=1, branch=worktree-alpha |
| Sidebar ⑂ badge persists after restart (live web-shell) | ❌ 0 fork icons | ✅ 1 fork icon (⑂ Refactor auth) |
POST /session/:id/load returns worktree metadata |
n/a | ✅ worktree-alpha |
| cwd relocated back into the worktree on load | n/a | ✅ git-scoped to worktree-alpha |
Create / isolation / ?cwd= scoping + containment / validation gates |
✅ | ✅ (no regression) |
==== full harness: SUMMARY: 12 passed, 0 failed ==== (was 11 passed, 1 failed on #7221)
PASS worktree session STILL LISTED after restart (1)
PASS worktree branch preserved in list (worktree-alpha)
PASS load response carries worktree metadata (worktree-alpha)
PASS worktree dir still git-scoped to worktree branch (worktree-alpha)
Notes
- Two minor items from the feat(web-shell): worktree-isolated sessions for parallel tasks #7221 review still stand (both non-blocking): the daemon-written sidecar leaves
originalBranch/originalHeadCommitas"", and creating a worktree makes the parent repo show.qwen/as untracked unless it is git-ignored. - This branch currently shows as conflicting with
mainon GitHub — worth a rebase before merge.
Nice fix — the restart path now behaves as the #7221 Test-Plan described. 👍
🇨🇳 中文版
复验 ✅ —— #7221 的重启持久化缺陷已修复
这是我在 #7221 上所报缺陷(daemon 重启后 worktree 会话从列表整行消失)的跟进复验。我在本分支(feat/worktree-restore-isolation,f7951df = #7221 + 这一个提交)上重新构建,并用同一套真实 daemon + 真实 git 脚本复跑。后端 12/12 全部通过,此前失败的重启场景已解决。
修复原理(为何能解决该缺陷):
sessionService.ts的sessionBelongsToCurrentProject:当记录的cwd(worktree 路径)哈希到不同 project 时,改为回退检查 sidecar 的originalCwd(主仓库根)。这正是此前把会话过滤掉的那道判断——因此 worktree 会话不再在 enrichment 之前被排除。restoreSessionHandler(POST /session/:id/load·/resume):现在会读取 sidecar →changeSessionCwd回到 worktree →setSessionWorktree写回 bridge entry → 在 load 响应中返回worktree。因此加载时紫色 chip 与 cwd 隔离也会恢复;若 worktree 目录已被删除则优雅降级。
复验结果(真实 qwen serve daemon,未 mock GitWorktreeService):
- 重启后 worktree 会话仍在列表中且带
worktree元数据:feat(web-shell): worktree-isolated sessions for parallel tasks #7221 ❌(total=0)→ feat(daemon): restore worktree isolation on session load/resume #7262 ✅(total=1,branch=worktree-alpha) - 重启后侧边栏 ⑂ 徽标保留(真实 web-shell):feat(web-shell): worktree-isolated sessions for parallel tasks #7221 ❌(0 个 fork 图标)→ feat(daemon): restore worktree isolation on session load/resume #7262 ✅(1 个,
⑂ Refactor auth) POST /session/:id/load返回worktree元数据:✅worktree-alpha- 加载时 cwd 重新定位回 worktree:✅ git 作用域为
worktree-alpha - 创建 / 隔离 /
?cwd=作用域与包含校验 / 校验门:✅(无回归)
完整脚本:12 passed, 0 failed(#7221 上为 11 passed, 1 failed)。
备注: #7221 复审中的两个次要项仍在(均非阻塞):daemon 写入的 sidecar 中 originalBranch/originalHeadCommit 为空串;创建 worktree 会让父仓库把 .qwen/ 报为未跟踪(除非 gitignore)。此外本分支目前与 main 冲突,合并前建议 rebase。
修复很到位——重启路径现在符合 #7221 测试计划的预期。👍
Re-verified locally on Linux against a real qwen serve daemon + real git repo (no GitWorktreeService mocking) and the production web-shell in a headless browser.
doudouOUC
left a comment
There was a problem hiding this comment.
Review:worktree isolation 重启恢复
整体方向正确、实现基本可靠——补上了 #7221 遗留的“重启后 worktree 会话从列表消失、session-list.ts 的 sidecar enrichment 成为死代码”缺口。类型安全、workspace 归属、sidecar 写读一致性、幂等性均已核对通过。建议合并前先看下面两点。
建议合并前处理
1. 归档的 worktree 会话重启后仍会消失(修复不完整,正好在本 PR 范围内)
packages/core/src/services/sessionService.ts › sessionBelongsToCurrentProject 新增的第三个检查只读 active 状态 sidecar:
readWorktreeSession(this.getWorktreeSessionPath(sessionId)) // 仅 active但 archiveSessions 会把 sidecar 从 active 移动到 archived 目录。于是已归档的 worktree 会话重启后三个检查全部失败被过滤,导致 packages/cli/src/serve/server/session-list.ts 里已经用 getWorktreeSessionPathForArchiveState(..., 'archived') 正确处理 archived 的 enrichment 分支依旧是死代码——正是本 PR 想消灭的同类问题。
→ membership 检查建议同时尝试 active + archived 两个 sidecar 路径(或让方法感知 state)。
2. 恢复路径绕过了防篡改的路径包含校验
共享 helper restoreWorktreeContext(TUI / headless / ACP 三个 resume 入口共用)会校验 worktreePath 必须位于 <originalCwd>/.qwen/worktrees/ 之下,用于防止被篡改的 sidecar 把文件操作重定向到 /etc、~/ 等。
本 PR 在 packages/cli/src/serve/routes/session.ts 的 load/resume 直接 readWorktreeSession + changeSessionCwd,跳过了该校验,成为第 4 个偏离共享约定的 resume 入口。唯一兜底是 agent 侧 sessionCd 的 folder-trust 检查,而它在 folder trust 关闭时是 no-op。
→ 建议复用 restoreWorktreeContext(顺带白拿 stale-sidecar 清理与目录存活检查),或至少补上同样的 expectedParent 包含校验。
建议
3. 缺测试:membership 第三检查、load/resume 恢复、setSessionWorktree 三个新行为均无覆盖。该路径已回归过一次,建议补一个重启恢复的回归测试。
次要 / Nit
setSessionWorktree(packages/acp-bridge/src/bridge.ts)只改内存不广播,其他订阅者要等重新拉取才看到 ⑂ chip;对照updateSessionMetadata是会广播的。- 恢复失败的
catch {}静默吞错——无daemonLog.warn(POST 路径有),也不清理 stale sidecar(restoreWorktreeContext会)。反复恢复失败时缺排查线索。 - load/resume handler 里
new SessionService(workspaceCwd)构造了两次(metadata 一次、取 sidecar 路径一次),可复用一个实例。
已核对通过
类型(BridgeRestoredSession extends BridgeSession 含 worktree?,与 wt 的 {slug,path,branch} 一致,Object.assign 合法)、写读一致(写侧 originalCwd = workspaceCwd 且各 project 独立 chats 目录,无跨项目误判)、主 workspace 会话在 check1 短路无额外 I/O、!session.worktree 保证不重复 cd、load/resume 全程用解析出的 runtime 未回退 primary、core barrel 已 export * 导出 readWorktreeSession。
…-isolation # Conflicts: # packages/cli/src/serve/routes/session.ts
bf579b0 to
c4b8b22
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
Fixes for doudouOUC review (c4b8b22)1. Archived sidecar path ✅ — 2. Path containment validation ✅ — Added Nits fixed:
Acknowledged (follow-up):
|
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 4 scenario(s). — Qwen Code · serve A/B |
wenshao
left a comment
There was a problem hiding this comment.
— qwen3.8-max-preview via Qwen Code /review
Re-review @
|
Replace the hand-rolled containment check (startsWith, bypassable by '..' traversal, broken on Windows) with the shared restoreWorktreeContext helper which uses path.resolve + path.sep (cross-platform), validates directory liveness, and clears stale sidecars automatically.
Fix for re-review (f184757)Critical: containment check bypassable by Replaced the hand-rolled
#4 originalCwd vs workspace cwd: Acknowledged. For monorepo subdirectory workspaces ( #5 Tests: Acknowledged. Will add regression tests for membership check, load/resume restore, and the |
|
@qwen-code /takeover |
Re-review @
|
|
Qwen Code review paused — model quota exhausted. Qwen review stopped: the model API quota is exhausted (reset at 07-20 07:32:00 UTC.). Transient errors auto-retry, but a quota reset is too far out to wait on a runner. Re-run once it resets by commenting |
1. sessionBelongsToCurrentProject: replace sidecar-based membership with path-based inference (extract repo root from recordCwd's .qwen/worktrees/ segment). Durable — survives sidecar cleanup when the worktree is removed. 2. Pre-read the worktree sidecar BEFORE loadSession/resumeSession to avoid the race where #restoreWorktreeOnResume clears it during load. Eliminates the double restoreWorktreeContext call.
Fixes for re-review @ f184757 (0d24900)#1 Sidecar cleanup destroys membership signal ✅ Replaced sidecar-based membership with path-based inference in #2 Double restoreWorktreeContext call ✅ Pre-read the sidecar with #3 originalCwd vs workspace cwd: The path-based membership check in #1 sidesteps this — it infers the repo root from the transcript cwd, not from the sidecar originalCwd. For monorepo subdirectory workspaces, #4 Tests: Acknowledged, will add in follow-up. Minor (Object.assign): Kept for type compatibility with |
ReviewThe PR closes the restart-persistence gap from #7221 in two places: a durable, marker-based membership check in One significant issue, then smaller points. 1. The sidecar pre-read defeats the ACP layer's validation — read it after load instead
The fix is simpler than the current code: read the sidecar after the load call returns. On the healthy path const session = await archiveCoordinator.runSharedMany(/* … */);
// …
const sidecar = await readWorktreeSession(
new SessionService(workspaceCwd).getWorktreeSessionPath(sessionId),
).catch(() => null);
if (!session.worktree && sidecar) { /* changeSessionCwd + setSessionWorktree */ }(The bridge awaits the agent-side 2.
|
1. Read worktree sidecar AFTER loadSession/resumeSession instead of before, so the route inherits the ACP layer's verdict — dead/tampered sidecars are already cleared by #restoreWorktreeOnResume, closing the containment bypass. 2. Reorder sessionBelongsToCurrentProject: marker-based worktree check (pure string ops) before readRuntimeStatus (file I/O). 3. Object.assign → plain session.worktree = wt (BridgeRestoredSession extends BridgeSession which declares worktree?). 4. Add comment documenting the !res.writable early-return asymmetry.
Fixes for review (8fa7125)#1 Pre-read defeats ACP validation ✅ — Moved sidecar read to AFTER load/resume. On dead/tampered paths, #2 Marker check ordering ✅ — Reordered Minor fixes:
#3 Tests: Acknowledged, will add in follow-up. |
Re-review @
|
|
Re-review confirmed all fixes ✅. PR description updated to match the marker-based implementation (no more sidecar-originalCwd references). Tests deferred to follow-up. |
|
Qwen Code review timed out. Qwen review timed out after 300 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
doudouOUC
left a comment
There was a problem hiding this comment.
复审当前 head 8fa7125,仍有两个阻塞问题,详见 inline comments;结论上暂不建议合并。
另外建议在本 PR 补回归测试,而不是继续延后:至少覆盖 nested worktree membership、健康/失效/tampered sidecar 的 load/resume 恢复,以及 bridge worktree metadata 更新。当前 Ubuntu Test、Serve A/B、Web Shell smoke 已通过,但 CLI integration、macOS 和 Windows job 被跳过。
doudouOUC
left a comment
There was a problem hiding this comment.
Re-review @ 2260f42:已确认 sidecar 自声明 foreign root、静态 symlink escape 和 realpathSync.native 回归都已修复,nested worktree membership 也保持正确;但当前完整复审仍有下面 2 个 Critical,因此 approval 暂缓。此前的 reattach 建议未变化,不重复评论。
|
Re: review @ af19e30 — both blocking items fixed in 2260f42:
|
The containment check now tries workspaceCwd first, then falls back to the git repo top-level (via GitWorktreeService.getRepoTopLevel). This fixes the monorepo regression where worktrees at <repoRoot>/.qwen/ worktrees/ were rejected when the workspace is a subdirectory. Also wraps getRepoTopLevel in try-catch (not .catch) since the mock throws synchronously when the method doesn't exist.
Fixes for doudouOUC re-review @ 2260f42 (94c882e)Critical #1 — monorepo worktree restore regression ✅ Critical #2 — TOCTOU across async bridge/prompt queue — Acknowledged as a theoretical concern. The route passes the canonical |
doudouOUC
left a comment
There was a problem hiding this comment.
Re-review @ 94c882e:已用真实 Git 验证 repo-top fallback 修复了 monorepo subdirectory 恢复,并保持 foreign repo、静态 symlink 和 nested membership 防护;此前的 realpathSync.native 修复也保持有效。但最终 relocation race 仍可在默认配置下复现,详见下方 1 个 Critical,因此 approval 暂缓。其余既有 Suggestions 不重复。
doudouOUC
left a comment
There was a problem hiding this comment.
Re-review after the author reply @ 94c882e:完整 diff 未变;已确认此前 Critical 中 monorepo、foreign/static-link containment、nested membership 与测试 mock 均已修复。作者也确认最终 relocation race 真实存在,但将其 defer 到 follow-up 不能关闭当前 PR 引入的自动恢复边界,因此仍保留下面 1 个 Critical,approval 暂缓。
…boundary Add optional allowedRoots to ChangeSessionCwdRequest. The agent-side sessionCd handler verifies (after its own fs.realpath) that the canonical target is under one of the allowed roots. Only set by the daemon's worktree create/restore paths; direct user cd omits the field, preserving existing behavior. This closes the TOCTOU between the route-level validation and the final relocation boundary — a symlink swap between validation and the queued sessionCd consumption is now caught at the relocation boundary itself.
The mock GitWorktreeService throws synchronously when getRepoTopLevel doesn't exist. .catch() only handles rejected promises, not synchronous throws. Use try-catch to match the restore path.
wenshao
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and server.test.ts could not be collected locally due to pre-existing Terminal import error.
— qwen3.8-max-preview via Qwen Code /review
doudouOUC
left a comment
There was a problem hiding this comment.
完整复审 @ 0d4aab9:此前的 repo 外 TOCTOU 变体现在会在最终 sessionCd 被拒绝,monorepo root、nested membership 和 realpathSync.native 的修复也仍成立。但新的最终策略仍有两处可复现的 Critical:允许范围宽到整个 repo,以及 containment 在 no-op 之后才执行。两者都可能让自动 worktree create/restore 在主 checkout 中成功返回,因此暂不 approve。15:03 已有的 Suggestions 和旧线程未重复提交。
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 5 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here. Not reviewed: Agent 0: Issue fidelity & root-cause ownership, Agent 1a: Line-by-line correctness, Agent 2: Security, Agent 3: Code quality, Agent 4: Performance & efficiency, Agent 5: Test coverage, Agent 6a: Undirected audit — attacker mindset, Agent 6b: Undirected audit — 3 AM oncall mindset, Agent 6c: Undirected audit — six-months-later maintainer, Agent 1c: Cross-file tracer, Agent 7: Build & test verification — its prompt was built, but no agent on record was launched with it. Not reviewed: reverse audit — an auditor ran and opened its brief, but no agent was launched with the prompt the CLI built — the launch was written by hand, and what the agent was actually asked is not what this skill certifies. Not reviewed: verification — a verifier ran and opened its brief, but no agent was launched with the prompt the CLI built — the launch was written by hand, and the posted findings cannot be counted as verified against it.
— qwen3.7-max via Qwen Code /review
…re no-op 1. allowedRoots now points to <root>/.qwen/worktrees (not the whole repo), so a symlink .qwen/worktrees/task -> <repo>/src is rejected by the containment check. 2. Moved the allowedRoots containment check before the no-op check in sessionCd. A no-op cd to a directory outside the allowed roots is now rejected instead of silently succeeding.
Fixes for doudouOUC re-review @ 0d4aab9 (5efdb97)Critical #1 — allowedRoots too broad ✅ Critical #2 — no-op bypass ✅ Replies to wenshao/qwen3.8-max-preview suggestions (4736163252)
|
doudouOUC
left a comment
There was a problem hiding this comment.
完整复审 @ 5efdb97:上一轮两条 Critical 均已修复。exact-head 临时目录复验 21/21 通过,覆盖 managed subtree、repo 内/外越界、queued symlink swap、managed-root link、no-op、真实 monorepo 与 Windows 路径;create/restore/bridge/direct-cd 的全部生产消费者也已追完,未发现新的阻断问题。剩余测试覆盖等 Suggestions 已在 PR 中记录为 follow-up,按仓库 5+ 轮规则不阻塞本次合并。
Code Review @
|
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. 3 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.
— qwen3.7-max via Qwen Code /review
Code ReviewIndependent proposal: Given the problem (worktree sessions filtered out because Comparison with the diff: The PR's approach matches this almost exactly. The implementation is clean and well-layered:
Non-blocking observations:
No critical blockers or AGENTS.md violations found. Test ResultsUnit tests — all pass:
Typecheck — all three changed packages compile cleanly ( Real-Scenario TestingDaemon startup and session lifecycle verified in tmux. The daemon starts cleanly with the PR code, session create/load endpoints respond correctly, and the worktree directory structure is created as expected: The full restart-persistence flow (write sidecar → restart daemon → verify session appears with ⑂ badge) requires a Web Shell UI interaction to create a proper session transcript, which is beyond headless tmux scripting. The 6 new unit tests cover this logic comprehensively: sidecar-present restore, no-sidecar skip, changeSessionCwd failure graceful degradation, and containment-check rejection. 中文说明代码审查: PR 的方案与独立提案几乎完全一致。实现干净、分层清晰:
非阻塞观察:
测试: 886 个单元测试全部通过(含 6 个新增 worktree 测试),三个包类型检查均通过。 实测: daemon 正常启动,会话创建/加载端点响应正确。完整的重启持久化流程需要 Web Shell UI 交互,6 个新单元测试已全面覆盖该逻辑。 — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 4/5 — solid fix for a real gap, clean implementation, comprehensive tests; only non-blocking nits (agent-side This PR does exactly what it sets out to do — close the restart-persistence gap in worktree sessions — and does it well. The two-path fix (listing via marker-based membership, load/resume via sidecar restore) is the right decomposition. The security posture is thoughtful: containment checks at both the daemon and agent layers, server-derived allowed roots, symlink resolution, and narrowing to The code reads well — comments explain the why (nested worktrees, TOCTOU elimination, ACP layer verdict inheritance), not the what. The 6 new unit tests cover the happy path, the no-sidecar path, the failure path, and the containment-rejection path. 886 total tests pass, typecheck clean across all three changed packages. If I had to maintain this in six months, I'd thank the author — the layering is clear, the failure modes are handled, and the tests tell me what each path does. 中文说明置信度:4/5 — 对真实缺陷的可靠修复,实现干净,测试全面;仅有非阻塞小问题(agent 侧 PR 精确完成了目标——修复 worktree 会话的重启持久化缺陷。双路径修复(列表用标记归属、加载/恢复用 sidecar 还原)分解合理。安全考虑周到:daemon 和 agent 双层容器检查、服务器派生允许根、符号链接解析、收窄到 代码可读性好——注释解释"为什么"而非"做什么"。6 个新单元测试覆盖正常路径、无 sidecar 路径、失败路径和容器拒绝路径。886 个测试全部通过,三个包类型检查干净。 — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
…wenLM#7355) * feat(autofix): render the managed fleet into the scan's run summary Seeing whether the loop was healthy meant reconstructing it by hand: list the bot's PRs, fetch each one's comments, regex the autofix-eval markers for round and watermark, then cross-check gh pr checks and the fork/takeover state. That is how today's triage of QwenLM#7246, QwenLM#7259, QwenLM#7329, QwenLM#7333 and QwenLM#7336 was done, and it is why a stalled PR stayed invisible until somebody went looking for it. The scan already computes every one of those facts while deciding what to process — it just wrote them to a job log nobody reads. Each per-PR terminal decision now also records a row, and the step renders one markdown table into the run summary: | PR | State | Detail | | QwenLM#7329 | SELECTED | 1 review + 5 inline new (round 0/5) | | QwenLM#7333 | idle | nothing new since 2026-07-20T13:54:18Z | | QwenLM#7262 | waiting | active checks in flight | | QwenLM#7208 | round-capped | round 100/100 - needs a human or @qwen-code /retry | States cover every branch that ends a PR's inspection: busy, skipped, unknown, waiting, round-capped, idle and SELECTED — so a PR cannot drop out of the table by returning early, which is exactly the invisibility this fixes. No new API calls (the data is already in hand), no writes outside the run summary, and the helper is defined at the top of the step so it stays clear of the BUSY_PRS/INSPECTED proximity guard that keeps the free busy-skip from consuming the inspection budget. Tests: the real helper and render block are replayed over fixtures (table structure, one row per state, and an empty fleet still rendering a table), plus each decision branch is pinned to its fleet_row. Mutation-verified: dropping one branch's row turns it red. * fix(autofix): use temp file for fleet test replay; cover fork-head skip (QwenLM#7355) * test(autofix): assert each skipped fleet_row call site individually (QwenLM#7355) * fix(autofix): record fleet rows for both budget-break paths (QwenLM#7355) The candidate-inspection budget break incremented INSPECTED but never called fleet_row, so the PR that tripped the budget was silently absent from the fleet table. The target-budget break left all remaining candidates invisible with no truncation signal. Add a per-PR deferred row before the inspection-budget break and a summary deferred row before the target-budget break so the fleet table stays complete in both cases. * fix(autofix): harden fleet summary render and clean up temp file (QwenLM#7355) Address review feedback: - Escape '|' in detail values to prevent broken table columns - Render budget summary row (PR '-') as em dash instead of '#-' - Add trap for FLEET_FILE cleanup on early exit paths - Document deferred summary row semantics in test comment * fix(autofix): use summary row for candidate-inspection budget break (QwenLM#7355) --------- Co-authored-by: wenshao <wenshao@example.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Released in v0.20.1. |

What this PR does
Fixes the restart-persistence gap identified in the E2E verification of #7221: after daemon restart, worktree sessions disappeared from the session list entirely because
SessionService.sessionBelongsToCurrentProjectcomparedgetProjectHash(recordCwd)against the workspace project hash — and the worktree path has a different hash.Two fixes:
Session listing (
SessionService.sessionBelongsToCurrentProject): Added a marker-based membership check — if the session's transcriptcwdcontains a.qwen/worktrees/segment, the repo root is inferred from the path and its project hash is compared. This is durable (no sidecar dependency, survives worktree removal) and pure string ops (no file I/O), so it runs before thereadRuntimeStatusfile read.Session load/resume (
POST /session/:id/loadand/resume): After loading a session, reads the worktree sidecar and — if found — callschangeSessionCwdto relocate the session into the worktree andsetSessionWorktreeto populate the bridge entry. The load/resume response includes the worktree metadata so the Web Shell restores the purple ⑂ chip.Also adds
setSessionWorktree(sessionId, worktree)to theAcpSessionBridgeinterface for populating worktree metadata on existing entries.Why it is needed
#7221 added worktree session support with sidecar persistence, but the sidecar enrichment in
session-list.tswas dead code on the restart path — sessions were filtered out bySessionServicebefore the enrichment loop ran. This PR closes that gap.Reviewer Test Plan
How to verify
npm run build && npm run dev:daemonCtrl+C+npm run dev:daemon)Tested on
Risk & Scope
sessionBelongsToCurrentProjectadds pure string operations (no file I/O) per non-matching session during listing. The sidecar read on the load/resume path is one small file read (<1KB) per worktree session load.changeSessionCwdfails silently and the session continues in the main workspace.Linked Issues
Follow-up to #7221 (E2E verification comment: restart-persistence gap)
中文说明
修复 #7221 E2E 验证发现的重启持久化缺陷:daemon 重启后 worktree 会话从列表消失。
根因:
SessionService.sessionBelongsToCurrentProject用getProjectHash(recordCwd)判断归属,worktree 路径的 hash 与主 workspace 不同,导致会话被过滤。修复:
.qwen/worktrees/段推断 repo root,纯字符串操作无文件 I/OchangeSessionCwd重定位 →setSessionWorktree填充 bridge entry